grammar Lua;

options 
{
    language=CSharp2; 
// ASTLabelType=CommonTree;
//    output=AST;
    backtrack = true;
    memoize = true;
}

/*tokens 
{
    	PROGRAM;
}*/

@lexer::namespace		{PSLib.Parsing.Lua}
@lexer::header 
{
	using System.Collections.Generic;
	using System.Text;
}

@lexer::members {
    public bool noCloseAhead(int numEqSigns) {
        if(input.LA(1) != ']') return true;
        for(int i = 2; i < numEqSigns+2; i++) {
            if(input.LA(i) != '=') return true;
        }
        return input.LA(numEqSigns+2) != ']';
    }

    public void matchClose(int numEqSigns){
        StringBuilder eqSigns = new StringBuilder();
        for(int i = 0; i < numEqSigns; i++) {
            eqSigns.Append('=');
        }
        Match("]"+eqSigns+"]");
    }
}

@parser::namespace		{PSLib.Parsing.Lua}
@parser::header 
{
	using System.Linq;
	using System.Collections.Generic;
	using PSLib.Parsing.Lua.ObjectModel;
	using PSLib.Parsing.Lua.Context;
	using PSLib.Parsing.Common;
}
@parser::init 
{
adasd
}
@parser::members 
{
	public ContextProvider ContextProvider 
	{ 
		get 
		{ 
			if(contextProvider == null) contextProvider = new ContextProvider(this);
			return contextProvider; 
		} 
	}
	internal ContextProvider contextProvider = null;
	
	public ObjectModelProvider ObjectModelProvider { get { return objectModelProvider; } }
	internal ObjectModelProvider objectModelProvider = new ObjectModelProvider();
		
	internal void SEn(string name)
	{
		objectModelProvider.EnterScope(name,this.CurrentElementPosition);
	}
	internal void SEx()
	{
		objectModelProvider.ExitScope(this.PrevElementPositionEnd);
	}
	
	internal ElementPosition CurrentElementPosition { get { return GetElementPosition(this.input.Index());} }
	internal ElementPosition PrevElementPositionStart { get { return GetElementPosition(this.input.Index() - 1);} }
	internal ElementPosition PrevElementPositionEnd { get { return GetElementPosition(this.input.Index() - 1,true); } }
		
	internal ElementPosition GetElementPosition(int tokenIndex, bool endPos = false)
   	{
		int line = 0;
		int charPosition = 0;
		if (tokenIndex < this.input.Count)
		{
		    IToken token = this.input.Get(tokenIndex);
		    line = token.Line;
		    charPosition = token.CharPositionInLine;
		    if(endPos) charPosition+= token.Text.Length;
		}
		else
		{
		    line = -1;
		    charPosition = -1;
		}
		
	    return new ElementPosition(line, charPosition);
   	}
	
	internal static class Temp
	{
		internal static Stack<Variable> currentVariableStack = new Stack<Variable>();
		internal static Stack<Stack<Variable>> currentVariablesTreeStack = new Stack<Stack<Variable>>();
		internal static string currentVariableKey = null;
		internal static ValueReference currentValueReference = null;
		internal static bool? currentFunctionSelfHidden = null;
		internal static ValueReference currentFunctionSelfValueReference = null;
	}
	
	public Dictionary<int,KeyValuePair<int,string>> LastFollowingContext { get; set; }
    public override object Match(IIntStream input, int ttype, BitSet follow)
    {
        string tok = ttype == -1? "eof" : tokenNames[ttype];
        var resultTokens = follow.ToArray().Where(tt=> tt != 1).Select(tt => tokenNames[tt]).ToList();

        object result = null;
        Exception exRes = null;
        try
        {
            result = base.Match(input, ttype, follow);
        }
        catch (System.Exception ex)
        {
            exRes = ex;
        }
        if (exRes != null || this.state.failed)
        {
            if (this.LastFollowingContext == null) this.LastFollowingContext = new Dictionary<int, KeyValuePair<int, string>>();
            if (!this.LastFollowingContext.ContainsKey(input.Index())) this.LastFollowingContext.Add(input.Index(), new KeyValuePair<int,string>(-1,""));
            if (this.state.followingStackPointer > this.LastFollowingContext[input.Index()].Key)
            {
                this.LastFollowingContext[input.Index()] = new System.Collections.Generic.KeyValuePair<int, string>(this.state.followingStackPointer, tok);
            }
        }

        if (exRes != null) throw exRes;

        return result;
    }
}

program	: ({SEn("Global block");} chunk {SEx();})?  EOF;
chunk : (stat (';')?)* (laststat (';')?)? ;

block : ( chunk? ) ;

stat  : var_assignment
		| functioncall 
		| do_block
		| while_stat
		| repeat_stat
		| if_stat
		| for_stat
		| func_definition
		| var_definition;
		
var_definition:	'local' nl=namelist ('=' el=explist1)? 
					{
						foreach(var item in $nl.variables)
							objectModelProvider.CurrentScope.AddVariable(item);
							
						Helper.AssignVariableDefinition($nl.variables, $el.valueReferenceRevisions);
					};

var_assignment	: vl=varlist1 '=' el=explist1
					{
						Helper.AssignVariableDefinition($vl.variables, $el.valueReferenceRevisions);
					};

func_definition @init{Variable variable = null; ElementPosition position = this.CurrentElementPosition; }
			: 'local' 'function' n=NAME 
					{
						variable = objectModelProvider.CurrentScope.GetVariable($n.Text, true);
						SEn("Function definition block");
					} 
				   fb1=func_body 
				   	{
				   		SEx();
				   		if($fb1.valueReference != null)
				   		{
				   			variable.SetValue(new ValueReferenceRevision(position,$fb1.valueReference));
				   		}
				   	}
				| 'function' fVar=funcname 
					{
						variable = $fVar.variable;
						SEn("Function definition block");
					} 
				fb2=func_body 
					{
						SEx();
						if($fb2.valueReference != null && variable != null)
				   		{
				   			variable.SetValue(new ValueReferenceRevision(position,$fb2.valueReference));
				   		}
					};

for_stat : 	'for' n=NAME '=' e=exp ',' exp (',' exp)? 'do' 
					{
						SEn("For block");
						
						Variable variable = new Variable();
						variable.Name = $n.text;
						variable.SetValue($e.valueReferenceRevisions[0]);
						
						objectModelProvider.CurrentScope.AddVariable(variable);
					} 
					block 
					{
						SEx();
					} 
					'end' 
				| 	'for' nl=namelist 'in' explist1 'do' 
						{
							SEn("For block");
							foreach(var item in $nl.variables)
								objectModelProvider.CurrentScope.AddVariable(item);
						} 
					block {SEx();} 'end' ;
				
if_stat  :'if' exp 'then' {SEn("If block");} block {SEx();} ('elseif' exp 'then' {SEn("Elseif block");} block {SEx();})* ('else' {SEn("Else block");} block {SEx();})? 'end';

laststat : 'return' (explist1)? | 'break';

repeat_stat : 'repeat' {SEn("Repeat block");} block {SEx();} 'until' exp ;

while_stat	: 'while' exp 'do' {SEn("While block");} block {SEx();} 'end';

do_block	: 'do' {SEn("Simple block");} block {SEx();} 'end'	;

varlist1 returns [List<Variable> variables] @init{$variables = new List<Variable>();}
			: v1=var 
				{
					$variables.Add($v1.variable);
				}
			(',' v2=var
				{
					$variables.Add($v2.variable);
				}
			)*;

funcname returns[Variable variable] 
	@init
	{
		Temp.currentVariablesTreeStack.Push(Temp.currentVariableStack);
		Temp.currentVariableStack = new Stack<Variable>();
		Temp.currentFunctionSelfValueReference = null;
		bool pathDefinition = false;
	}
			: nBase=NAME 
				{
					Variable baseVariable = objectModelProvider.CurrentScope.GetVariable($nBase.text,false,false);
					if(baseVariable != null) Temp.currentVariableStack.Push(baseVariable);
				}
			('.' nPath=NAME
				{
					if(Temp.currentVariableStack.Count != 0) 
					{
						Variable baseVariable = Temp.currentVariableStack.Peek();
						Variable memberVariable = Helper.GetTableVariable(baseVariable.GetLastRevisionValue(),$nPath.text);
						if(memberVariable != null) Temp.currentVariableStack.Push(memberVariable);
						else Temp.currentVariableStack.Clear();
					}
				}
			)* (':' nVar=NAME
				{
					if(Temp.currentVariableStack.Count != 0) 
					{
						Variable baseVariable = Temp.currentVariableStack.Peek();
						Variable memberVariable = Helper.GetTableVariable(baseVariable.GetLastRevisionValue(),$nVar.text);
						if(memberVariable != null) Temp.currentVariableStack.Push(memberVariable);
						else Temp.currentVariableStack.Clear();
					}
				}
			)? 				
	{
		if(Temp.currentVariableStack.Count != 0) 
		{
			if($nVar != null) Temp.currentFunctionSelfHidden = true;
			else if($nPath != null) Temp.currentFunctionSelfHidden = false;
			else Temp.currentFunctionSelfHidden = null;

			$variable = Temp.currentVariableStack.Pop();
			
			if(Temp.currentFunctionSelfHidden.HasValue && Temp.currentVariableStack.Count != 0) 
				Temp.currentFunctionSelfValueReference = Temp.currentVariableStack.Pop().GetLastRevisionValue();
		}
		Temp.currentVariableStack = Temp.currentVariablesTreeStack.Pop();
	};

namelist returns [List<Variable> variables] @init{$variables = new List<Variable>();}
			: n1=NAME 
				{
					$variables.Add(new Variable(){Name=$n1.text});
				}
			( ',' n2=NAME 
				{
					$variables.Add(new Variable(){Name=$n2.text});
				}
			)* ;

explist1 returns [List<ValueReferenceRevision> valueReferenceRevisions] 
	@init
	{
		$valueReferenceRevisions = new List<ValueReferenceRevision>();
		List<ValueReferenceRevision> tempReferenceRevisions = null;
	}
			: (e1=exp
				{
					if(tempReferenceRevisions != null)
					{
						$valueReferenceRevisions.Add(tempReferenceRevisions[0]);
						tempReferenceRevisions = null;
					}
					tempReferenceRevisions = $e1.valueReferenceRevisions;
				} 
			  ',')* e2=exp 
			  	{
			        if(tempReferenceRevisions != null)
					{
						$valueReferenceRevisions.Add(tempReferenceRevisions[0]);
						tempReferenceRevisions = null;
					}
			        $valueReferenceRevisions.AddRange($e2.valueReferenceRevisions);
			  	};

exp returns[List<ValueReferenceRevision> valueReferenceRevisions] 
	@init
	{ 
		$valueReferenceRevisions = new List<ValueReferenceRevision>();
		ElementPosition position = this.CurrentElementPosition; 
		List<ValueReference> valueRefs = new List<ValueReference>(); 
		IList lst11 = this.GetRuleInvocationStack();
	}
		:(
			( 'nil' 			
			| 'false' 				{ valueRefs.Add(ValueReference.Create(false)); 		}
			| 'true'				{ valueRefs.Add(ValueReference.Create(true)); 		}
			| n=number				{ valueRefs.Add(ValueReference.Create($n.text)); 	}
			| st=stringToken   		{ valueRefs.Add(ValueReference.Create($st.text)); 	}
			| '...' 
			| fu=function 			{ valueRefs.Add($fu.valueReference); }
			| pe=prefixexp 			{ valueRefs.AddRange($pe.valueReferences); }
			| tc=tableconstructor 	{ valueRefs.Add($tc.valueReference); }
			| unop exp			) (binop exp)* )
			{
				if(valueRefs.Count == 0) valueRefs.Add(ValueReference.Create());
				foreach(var item in valueRefs) 
				{
					ValueReference resultItem = item;
					if(resultItem == null) resultItem = ValueReference.Create();
					$valueReferenceRevisions.Add(new ValueReferenceRevision(position, resultItem));
				}
			};
	 
binop : '+' | '-' | '*' | '/' | '^' | '%' | '..' | '<' | '<=' | '>' | '>=' | '==' | '~=' | 'and' | 'or';

var returns[Variable variable] 
@init
	{
		Temp.currentVariablesTreeStack.Push(Temp.currentVariableStack);
		Temp.currentVariableStack = new Stack<Variable>();
	}
		: ( n=NAME 
			{
				Variable result = objectModelProvider.CurrentScope.GetVariable($n.text,false,false);
                if(result != null) Temp.currentVariableStack.Push(result);
			}
		| '(' exp ')' varSuffix[false] ) ( vs=varSuffix[true] 
			{
				if(Temp.currentVariableStack.Count != 0)
				{
					if($vs.variable != null) Temp.currentVariableStack.Push($vs.variable);
					else Temp.currentVariableStack.Clear();
				}
			}
		)* 
			{
				if(Temp.currentVariableStack.Count != 0) $variable = Temp.currentVariableStack.Pop();
				Temp.currentVariableStack = Temp.currentVariablesTreeStack.Pop();
			};

prefixexp returns[List<ValueReference> valueReferences] 
	@init
	{ 
		$valueReferences = new List<ValueReference>(); 
		List<ValueReference> tempReferences = null;
	}
			: voe=varOrExp 
				{
					Temp.currentValueReference = $voe.valueReference;
				}
					(naa=nameAndArgs
				{
					tempReferences = $naa.valueReferences;
					if(tempReferences.Count == 0) Temp.currentValueReference = ValueReference.Create();
					else Temp.currentValueReference = tempReferences[0];
				}
					)* 
				{
					if(tempReferences != null) $valueReferences.AddRange(tempReferences);
					else $valueReferences.Add($voe.valueReference);
					Temp.currentValueReference = null;
				};

functioncall: varOrExp nameAndArgs+;

varOrExp returns[ValueReference valueReference]
			: v=var 
				{ 
					if($v.variable != null) 
						$valueReference = $v.variable.GetLastRevisionValue(); 
				} 
			| '(' ex=exp ')' 
				{
					$valueReference = $ex.valueReferenceRevisions[0].ValueReference;
				} ;

nameAndArgs returns[List<ValueReference> valueReferences] @init{$valueReferences = new List<ValueReference>();}
			: (':' n=NAME )? args 
				{
					if(Temp.currentValueReference != null)
					{
						Function functionValue = null;
						if(string.IsNullOrEmpty($n.text))
						{
							functionValue = Temp.currentValueReference.Value as Function;
						}
						else
						{
							Metatable table = Temp.currentValueReference.Value as Metatable;
							if(table != null && table.Contains($n.text))
							{
								functionValue = table[$n.text].GetLastRevisionValue().Value as Function;
							}
						}
						if(functionValue != null)
						{
							$valueReferences.AddRange(functionValue.Returns);
						}
					}
				}; 

varSuffix[bool recognizeVariable] returns[Variable variable]  @init{ $variable = null; }
		: (nameAndArgs {$recognizeVariable = false;} )* ( '[' ex=exp ']'
			{
				if(recognizeVariable && $ex.valueReferenceRevisions.Count > 0)
				{
					ValueReferenceRevision exRevision = $ex.valueReferenceRevisions[0];
                    Temp.currentVariableKey = exRevision.ValueReference.Value as string;
					if(exRevision.ValueReference.Type == ValueReferenceType.String) 
					{
						Temp.currentVariableKey = exRevision.ValueReference.Value as string;
					}
					else if(exRevision.ValueReference.Value is int) 
					{
						Temp.currentVariableKey = Metatable.ConvertKey((int)exRevision.ValueReference.Value);
					}
					else Temp.currentVariableKey = null;
				}
			} 
		| ('.' n=NAME 
			{
				if(recognizeVariable) Temp.currentVariableKey = $n.text;
				else Temp.currentVariableKey = null;
			}
			))
			{
				if($recognizeVariable && Temp.currentVariableStack.Count != 0)
				{
					ValueReference baseValueReference = Temp.currentVariableStack.Peek().GetLastRevisionValue();
                    
					if(baseValueReference.Type == ValueReferenceType.Table && !string.IsNullOrEmpty(Temp.currentVariableKey))
					{
						Metatable metatable = baseValueReference.Value as Metatable;
						if(!metatable.Contains(Temp.currentVariableKey)) metatable[Temp.currentVariableKey] = new Variable(){ Name=Temp.currentVariableKey };
						$variable = metatable[Temp.currentVariableKey];
					}	
				}
			};

args :  '(' (explist1)? ')' | tableconstructor | stringToken ;

function returns[ValueReference valueReference]: {SEn("Function definition block"); } 'function' fb=func_body {$valueReference = $fb.valueReference; SEx(); };

func_body returns[ValueReference valueReference] @init{ Function func = new Function(); }
			: '(' (fpl=func_parlist )? ')' block 'end'
				{
					if($fpl.arguments != null)
					{
						func.Arguments.AddRange($fpl.arguments);
					}
					
					$valueReference = ValueReference.Create(func);
				};

func_parlist returns[List<FunctionArgument> arguments] @init{$arguments = new List<FunctionArgument>();}
				:(nl=namelist 
					{
						if($nl.variables != null)
						{
							foreach(var item in $nl.variables)
							{
								if(Temp.currentFunctionSelfHidden.HasValue)
								{
									if(Temp.currentFunctionSelfHidden.Value)
									{
										Variable selfVar = new Variable();
										selfVar.Name = "self";
										selfVar.SetValue(this.CurrentElementPosition, Temp.currentFunctionSelfValueReference);
										
										objectModelProvider.CurrentScope.AddVariable(selfVar);
									}
									else
									{
										item.SetValue(this.CurrentElementPosition, Temp.currentFunctionSelfValueReference);
									}
									
									Temp.currentFunctionSelfHidden = null;
								}
								objectModelProvider.CurrentScope.AddVariable(item);
								$arguments.Add(new FunctionArgument(item.Name));
							}
						}
					}
				(',' '...' { $arguments.Add(new FunctionArgument("...")); })? | '...' { $arguments.Add(new FunctionArgument("...")); } ) ;

tableconstructor returns[ValueReference valueReference] @init{ Metatable metatable = new Metatable();}
			: '{' (fl=fieldlist)? '}'
				{
					if($fl.variables != null) metatable.AddRange($fl.variables);
					$valueReference = ValueReference.Create(metatable);
				}
			;

fieldlist returns [List<Variable> variables] @init{$variables = new List<Variable>();}
			: f1=field 
				{
					$variables.AddRange($f1.variables);
				}
			(fieldsep f2=field
				{
					$variables.AddRange($f2.variables);
				}
			)* (fieldsep)?;

field returns[List<Variable> variables]
	@init
	{
		$variables = new List<Variable>();
	}
			: '[' ex1=exp ']' '=' ex2=exp 
				{
					ValueReferenceRevision exRevision = $ex1.valueReferenceRevisions[0];
					if(exRevision.ValueReference.Type == ValueReferenceType.String) 
					{
						Variable variable = new Variable();
						variable.Name = exRevision.ValueReference.Value as string;
						variable.SetValue($ex2.valueReferenceRevisions[0]);
						
						$variables.Add(variable);
					}
				}
			| n=NAME '=' ex3=exp 
				{
					Variable variable = new Variable();
					variable.Name = $n.text;
					variable.SetValue($ex3.valueReferenceRevisions[0]);
					
					$variables.Add(variable);
				}
			| ex4=exp
				{
					foreach(var item in $ex4.valueReferenceRevisions)
					{
						Variable variable = new Variable();
						variable.Name = Variable.UnknownIndexName;
						variable.SetValue(item);
						
						$variables.Add(variable);
					}
				};

fieldsep : ',' | ';';

unop : '-' | 'not' | '#';

number : INT | FLOAT | EXP | HEX;

stringToken	: NORMALSTRING
			| CHARSTRING
			| LONGSTRING
			;
//Tokens

NAME  : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*  ;


LINE_COMMENT:   (
				  '--' ~('\n'|'\r')* (LINE_BREAK | EOF) 
				| ('#!' ~('\n'|'\r')* (LINE_BREAK | EOF))
				) {$channel=HIDDEN;};
COMMENT:'--' LONGSTRING {$channel=HIDDEN;};

WS  :   ( ' ' | '\t' | '\r' | '\n' )+ {$channel=HIDDEN;};

INT	: ('0'..'9')+;
FLOAT 	: INT? '.' INT ;

EXP	: (INT| FLOAT) ('E'|'e') ('-')? INT;

HEX	:'0x' ('0'..'9'| 'a'..'f' | 'A'..'F')+ ;

//String tokens
NORMALSTRING	: '"'  ( ~('"' |'\\'|'\n'|'\r') | '\\'.)* '"' ;
CHARSTRING		: '\'' ( ~('\''|'\\'|'\n'|'\r') | '\\'.)* '\'' ;
//Operator tokens
	
//Keyword tokens

//Chars
LONGSTRING
@init {int openEq = 0;}
  :  '[' ('=' {openEq++;})* '[' ({noCloseAhead(openEq)}?=> .)* {matchClose(openEq);}
  ;
  
fragment LINE_BREAK: '\r'? '\n';

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT;

